CS 5010: Problem Set 9

Out: Monday, March 21, 2016

Due: Monday, March 28, 2016 at 5pm.

Corrected: Monday, March 21, 2016 (spurious "right" changed to "left")

Corrected: Tuesday, March 22, 2016
(changed WorldState<%> to SimulatorState<%>, changed interface definitions to inherit from WordState<%> and from Widget<%>)

Corrected: Monday, March 28, 2016 (incorrect number of provided things changed to "thirteen")


The goal of this problem set is to help you design simple class systems and methods on them.

As always, you must follow the design recipe, in this case the OO Design Recipe and deliverables as spelled out in Lesson 9.5 and in deliverables.html Be sure to sync your work and fill out a Work Session Report at the end of every work session. Use the Work Session Report for PS09.

For the first time this semester, you will use the standard Racket language. No more student languages! Even so, you must restrict yourself to the language features discussed in class. In particular, you may NOT use state (set!) or inheritance.

You must use #lang racket, rackunit, "extras.rkt", 2htdp/universe, and 2htdp/image. So your file should begin something like

#lang racket
(require rackunit)
(require "extras.rkt")
(require 2htdp/universe)   
(require 2htdp/image)      
You may require "sets.rkt" if you need it. You may not require any other libraries.

The physics marketing department at that nearby university may have found a client for Perpetual Goofballs, but the client demands many changes to the screensaver-4 demonstration you delivered in Problem Set 04. For one thing, the client wants the program to be called a simulator instead of a screensaver.

The client also insists the simulator be written in Racket using object-oriented style. For security reasons the client insists the Racket libraries be limited to those listed above. The client also appears to have a fixation with video games of the early 1980s, so the Perpetual Goofballs will be even less perpetual than before. Several different kinds of robots will attempt to destroy the goofballs. You will also add Stealth Goofballs (SGs), which are harder for the robots to detect.

The arena expands to 800 pixels wide and 600 pixels high.

The client prefers graphics coordinates in which the origin (0,0) is at the top left, the x coordinate increases as you go to the right, and the y coordinate increases as you go down. The x and y coordinates now represent pixels, with the conversion from pixels to real-world coordinates left unspecified, apparently for security reasons.

The simulator is initially paused, and the arena starts out empty. The space bar pauses or unpauses the simulator.

There will be two kinds of goofballs, Stealth Goofballs and Round Goofballs. A Stealth Goofball is normally displayed as a gray outline square 35 pixels on a side, with the goofball's position considered to be at the center of that square. A Round Goofball is normally displayed as a blue outline circle of radius 20 pixels, and its position is at the center of that circle.

Whenever either kind of goofball touches any side of the arena, it automatically contracts just enough to remain confined to the arena. Contracted goofballs of either kind retain their general shape as they contract, but contracted goofballs of either kind are displayed as a red outline.

If either kind of goofball is displayed in its normal color (gray or blue), then its current velocity is displayed as text of the form (vx, vy), centered within the goofball's outline, in the same color (gray or blue), large enough to be legible but small enough to fit within the outline.

Both kinds of gooball are selectable and draggable. Depressing the mouse button with a goofball's outline causes it to be "selected". When a goofball is selected, its outline is displayed in green instead of gray, blue, or red, and its velocity is not displayed. The location where the mouse grabbed the goofball is displayed as a solid orange circle of radius 4. Simply pressing the mouse button without moving the mouse should not cause the goofball to move; in fact, it stops the goofball wherever it is, while unselected goofballs and robots continue to move about unless the simulator is paused.

Once a goofball of either kind has been selected, you should be able to drag it around the arena using a smooth drag, as in screensaver-4. As in screensaver-4, dragging a goofball near the edge of the arena may reduce its size. If the size is reduced so much that the location of the mouse no longer lies within or on the reduced outline, then the goofball becomes unselected and moves as determined by its velocity and whether the simulator is paused.

Selecting and dragging work regardless of whether the simulator is paused.

New Stealth Goofballs can be created by typing "n", which creates a new Stealth Goofball centered at position (50,75).

All newly created Stealth Goofballs are initially unselected and have an initial velocity of (+5,+3) pixels per tick.

The right arrow key ("right") increases the vx component of all selected goofballs by 1 pixel per tick, and the left arrow key ("left") decreases the vx component of all selected goofballs by 1 pixel per tick. The up arrow key ("up") decreases the vy component of the velocity by 1 pixel per tick, and the down arrow key ("down") increases the vy component by 1 pixel per tick.

Both kinds of goofballs rebound from the arena walls via a perfect bounce.

The goofballs can be attacked by robots, which are launched from a launcher located on the right hand (east) side of the arena. The launcher starts out at coordinates (800,500), but its position can be moved up ten pixels by typing "u" and can be moved down ten pixels by typing "d". If the launcher's y coordinate is 0, then any "u" event will be ignored. If the launcher's y coordinate is 600, then any "d" event will be ignored.

Typing "s" launches a Smart Anti-Goofball Robot (SAGR) whose initial velocity is (-10, -3) and whose initial position is 10 pixels to the left and 3 pixels above the current position of the launcher. Typing "r" launches a Dumb Anti-Goofball Robot (DAGR) whose velocity is (-5,-3) and whose initial position is 5 pixels to the left and 3 pixels above the current position of the launcher.

A SAGR is displayed as a solid orange square 11 pixels on a side, centered on the SAGR's position. A DAGR is displayed as a solid black square 15 pixels on a side, centered on the DAGR's position.

If a SAGR's or DAGR's square overlaps the square or circle of a goofball or another SAGR or DAGR at the beginning or end of a tick, then the SAGR or DAGR explodes and disappears from the simulation. If an exploding SAGR or DAGR overlaps the circle of a Round Goofball, then the Round Goofball is destroyed and immediately removed from the SimulatorState. If an exploding DAGR overlaps the square of a Stealth Goofball, then the Stealth Goofball is destroyed and immediately removed from the SimulatorState. If an exploding SAGR overlaps the square of a Stealth Goofball, then the Stealth Goofball is damaged and becomes a Round Goofball moving at the same velocity as before.

An animated demonstration of that behavior is available.

An explosion will also occur if two or more goofballs are at exactly the same position at the beginning or end of a tick. In that case, all goofballs that share the same position are immediately removed from the WorkState.

As in screensaver-4, the simulator should provide some suitably graphic indication of the site of a destroyed goofball, with the graphics limited to a 30cm radius around the goofball's last position, to persist no less than 10 ticks and no more than 100 ticks. The simulator should not show explosions that merely damage a Stealth Goofball or remove only SAGRs and/or DAGRs from the simulator.

SAGRs and DAGRs are not goofballs, so they do not bounce when they reach a side of the arena. If their velocity takes them outside the arena, they simply disappear from the simulator and never come back.

A DAGR's velocity never changes, so it will leave the arena unless it explodes first. A SAGR will run out of fuel after 400 ticks unless it explodes first. A SAGR that has run out of fuel disappears from the simulator and never comes back.

A SAGR (but not a DAGR) can detect Round Goofballs at all ranges and can detect a Stealth Goofball when the Euclidean distance between the center of the SAGR and the center of the Stealth Goofball is less than or equal to 50 pixels, as computed at the beginning of a tick. When a SAGR detects one or more goofballs at the beginning of a tick, it immediately changes its velocity as described by the following algorithm, and moves at its new velocity during the tick (unless the simulator is paused, in which case it doesn't move even though its velocity may change).

Your simulator program should be in a file named simulator.rkt and should provide the following thirteen interfaces and functions:

;;; make-world : ListOfGoofball<%> ListOfRobot<%> -> SimulatorState<%>
;;; GIVEN: a list of Goofball<%> and a list of Robot<%>
;;; RETURNS: a world with those goofballs and robots
;;;     and the launcher at its standard initial position

;;; world-after-tick : SimulatorState<%> -> SimulatorState<%>
;;; GIVEN: a SimulatorState<%>
;;; WHERE: the SimulatorState<%> was returned by one of these provided functions
;;; RETURNS: the SimulatorState<%> that should follow the given SimulatorState<%>
;;;     after a tick

;;; world-after-key-event : SimulatorState<%> KeyEvent -> SimulatorState<%>
;;; GIVEN: a SimulatorState<%> and a KeyEvent
;;; WHERE: the SimulatorState<%> was returned by one of these provided functions
;;; RETURNS: the SimulatorState<%> that should follow the given SimulatorState<%>
;;;     after the given KeyEvent

;;; world-after-mouse-event : SimulatorState<%> Int Int MouseEvent -> SimulatorState<%>
;;; GIVEN: A SimulatorState<%>, the x- and y-coordinates of a mouse event,
;;;     and the mouse event
;;; WHERE: the SimulatorState<%> was returned by one of these provided functions
;;; RETURNS: the SimulatorState<%> that should follow the given SimulatorState<%>
;;;     after the given mouse event

;;; run-world : PosInt SimulatorState<%> -> SimulatorState<%>
;;; GIVEN: a frame rate (in seconds/tick) and a SimulatorState<%>
;;; WHERE: the SimulatorState<%> was returned by one of these provided functions
;;; RETURNS: the final state of the world after running the given world

;;; run : PosNum -> SimulatorState<%> 
;;; GIVEN: a frame rate (in seconds/tick)
;;; RETURNS: the final state of the world after starting and running
;;;     the standard initial world

;;; make-stealth-goofball : Integer Integer Integer Integer -> Goofball<%>
;;; GIVEN: x and y coordinates and velocity components vx and vy
;;; RETURNS: A Stealth Goofball with its center at the x and y coordinates
;;;     and velocity components vx and vy

;;; make-round-goofball : Integer Integer Integer Integer -> Goofball<%>
;;; GIVEN: x and y coordinates and velocity components vx and vy
;;; RETURNS: A Round Goofball with its center at the x and y coordinates
;;;     and velocity components vx and vy

;;; make-DAGR : Integer Integer Integer Integer -> Robot<%>
;;; GIVEN: x and y coordinates and velocity components vx and vy
;;; RETURNS: A DAGR with its center at the x and y coordinates
;;;     and velocity components vx and vy

;;; make-SAGR : Integer Integer Integer Integer -> Robot<%>
;;; GIVEN: x and y coordinates and velocity components vx and vy
;;; RETURNS: A DAGR with its center at the x and y coordinates
;;;     and velocity components vx and vy

;;; Interfaces:

(define SimulatorState<%>
  (interface (WorldState<%>)  ;; this means: include all the methods in
                              ;; WorldState<%>
    
    ;; -> Integer
    ;; RETURN: the x and y coordinates of the launcher
    launcher-x
    launcher-y

    ;; -> ListOfGoofball<%>
    goofballs

    ;; -> ListOfRobot<%>
    robots

))

(define Goofball<%> 
  (interface (Widget<%>)  ;; this means: include all the methods in Widget<%>
 
    ;; -> Int
    ;; RETURNS: the x or y position of the center of this goofball
    get-x
    get-y

    ;; -> Int
    ;; RETURNS: the vx or vy component of this goofball's velocity
    get-vx
    get-vy

    ;; -> Boolean
    ;; RETURNS: true iff this goofball has the stealth of a Stealth Goofball
    stealthy?

    ))

(define Robot<%> 
  (interface (Widget<%>)  ;; this means: include all the methods in Widget<%>
 
    ;; -> Int
    ;; RETURNS: the x or y position of the center of this robot
    get-x
    get-y

    ;; -> Int
    ;; RETURNS: the vx or vy component of this robot's velocity
    get-vx
    get-vy

    ;; -> Boolean
    ;; RETURNS: true iff this robot has a SAGR's abilities
    smart?

    ))

Make sure your program is in a file named simulator.rkt.

When you do this problem, remember the principle of Iterative Development: get something simple working, and then add features as necessary.


Last modified: Mon Mar28 2016